package cz.tomasdvorak.cars.dao.impl;

import cz.tomasdvorak.cars.auth.MyAuthenticatedWebSession;
import cz.tomasdvorak.cars.dao.VehiclesDao;
import cz.tomasdvorak.cars.dto.*;
import cz.tomasdvorak.cars.url.VehicleImageUrlCreator;
import cz.tomasdvorak.cars.utils.Sets;
import cz.tomasdvorak.cars.utils.StringUtils;
import org.apache.log4j.Logger;
import org.apache.wicket.Session;
import org.apache.wicket.markup.html.form.upload.FileUpload;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;

import javax.sql.DataSource;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import java.util.Set;

// TODO: remove table names from code and move to config xml
public class VehiclesDaoImpl implements VehiclesDao {

    private static final Logger log = Logger.getLogger(VehiclesDaoImpl.class);

    private static final String USERS_SEPARATOR = ",";

    private DataSource dataSource;
    private SimpleJdbcTemplate jdbcTemplate;
    private VehicleImageUrlCreator imageUrlCreator;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
        jdbcTemplate = new SimpleJdbcTemplate(dataSource);
    }

    public void setImageUrlCreator(VehicleImageUrlCreator creator) {
        this.imageUrlCreator = creator;
    }

    private final RowMapper<Vehicle> VEHICLE_MAPPER = new RowMapper<Vehicle>() {
         public Vehicle mapRow(ResultSet rs, int i) throws SQLException {
             String typeString = rs.getString("type");
             VehicleBodyType type = null;
             if (typeString != null && !typeString.trim().isEmpty()) {
                 type = VehicleBodyType.valueOf(typeString);
             }
             return new Vehicle(rs.getInt("id"), rs.getString("registrationNumber"), rs.getString(
                     "manufacturer"), rs.getString("model"), rs.getInt("price"), rs.getInt(
                     "gasCapacity"), type, rs.getInt("ownerId"), parseUsers(rs.getString("users")), rs.getString("attachment_name") != null, VehiclesDaoImpl.this.imageUrlCreator);

         }

        private Set<Integer> parseUsers(String usersString) {
            Set<Integer> result = Sets.hashSet();
            String[] users = usersString.split(USERS_SEPARATOR);
            for (String item : users) {
                String user = item.trim();
                int id = Integer.parseInt(user);
                result.add(id);
            }
            return result;
        }
    };

    private final RowMapper<ImageItem> IMAGE_MAPPER = new RowMapper<ImageItem>() {
        public ImageItem mapRow(ResultSet rs, int i) throws SQLException {
            return new ImageItem(rs.getInt("id_vehicle"), new Date(), rs.getString(
                    "attachment_mime"), rs.getString("attachment_name"), rs.getBytes(
                    "attachment_data"));
        }
    };

    public Vehicle createNewDto() {
        Vehicle vehicle = new Vehicle();
        vehicle.setImageUrlCreator(this.imageUrlCreator);
        return vehicle;
    }

    public void insertOrUpdate(Vehicle vehicle) {
        MyAuthenticatedWebSession session = (MyAuthenticatedWebSession) Session.get();
        if(vehicle.isNew()) {
            insertVehicle(vehicle, session.getUser());
        } else {
            updateVehicle(vehicle, session.getUser());
        }
    }

    public List<Vehicle> getAllVehicles(VehiclesCriteria criteria) {
        if (criteria != null && criteria.getSortProperty() != null) {
            // TODO: is not nice to concat string to ensure ordering, but there are no other nice variants
            // of sorting using prepared statements
            return jdbcTemplate.query(addSortingToQuery("SELECT vehicle.*,attachment_name FROM tdvorak_vehicle LEFT JOIN tdvorak_attachments ON (id = id_vehicle)", criteria.getSortProperty(), criteria.isSortAsc()), VEHICLE_MAPPER);
        } else {
            return jdbcTemplate.query("SELECT vehicle.*,attachment_name FROM tdvorak_vehicle as vehicle LEFT JOIN tdvorak_attachments ON (id = id_vehicle)", VEHICLE_MAPPER);
        }
    }

    public Vehicle getById(int id) {
        return jdbcTemplate.queryForObject("SELECT vehicle.*,attachment_name FROM tdvorak_vehicle as vehicle LEFT JOIN tdvorak_attachments ON (id = id_vehicle) WHERE id=?", VEHICLE_MAPPER, id);
    }

    public void appendImage(Vehicle vehicle, FileUpload fileUpload) {
        jdbcTemplate.update("INSERT INTO tdvorak_attachments (id_vehicle,attachment_data,attachment_mime, attachment_name) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE attachment_data=?, attachment_mime=?, attachment_name=?",
                vehicle.getId(),
                fileUpload.getBytes(),
                fileUpload.getContentType(),
                fileUpload.getClientFileName(),
                fileUpload.getBytes(),
                fileUpload.getContentType(),
                fileUpload.getClientFileName()
            );
    }


    private String addSortingToQuery(String query, String orderBy, boolean asc) {
        return query + " ORDER BY " + orderBy + " " + (asc ? "ASC" : "DESC");
    }

    private void updateVehicle(Vehicle vehicle, User user) {

        int realUserId = user.getId();

        // admin has permissions to change every vehicle owner
        if (Role.ADMIN.equals(user.getRole())) {
            realUserId = vehicle.getOwnerId();
        // if previous owner is the same as current user, than he has permissions to change owner of vehicle
        } else if (getById(vehicle.getId()).getOwnerId() == user.getId()) {
            realUserId = vehicle.getOwnerId();
        }

        jdbcTemplate.update(
                "UPDATE tdvorak_vehicle SET registrationNumber=?, manufacturer=?, model=?,price=?,gasCapacity=?, type=?,ownerId=?,users=? WHERE id=?",
                vehicle.getRegistrationNumber(),
                vehicle.getManufacturer(),
                vehicle.getModel(),
                vehicle.getPrice(),
                vehicle.getGasCapacity(),
                vehicle.getType().name(),
                realUserId,
                concatUsers(vehicle.getUsers()),
                vehicle.getId()
            );
    }

    private String concatUsers(Set<Integer> users) {
        return StringUtils.join(users, USERS_SEPARATOR);
    }

    private void insertVehicle(Vehicle vehicle, User user) {
        int realUserId = Role.ADMIN.equals(user.getRole()) ? vehicle.getOwnerId() : user.getId();
        jdbcTemplate.update(
                "INSERT INTO tdvorak_vehicle (registrationNumber, manufacturer, model, price, gasCapacity, type, ownerId, users) VALUES(?,?,?,?,?,?,?,?)",
                vehicle.getRegistrationNumber(),
                vehicle.getManufacturer(),
                vehicle.getModel(),
                vehicle.getPrice(),
                vehicle.getGasCapacity(),
                vehicle.getType().name(),
                realUserId,
                concatUsers(vehicle.getUsers())
            );
    }

    public ImageItem getImage(int id) throws IOException {
        try {
            return jdbcTemplate.queryForObject("SELECT * FROM tdvorak_attachments where id_vehicle = ?", IMAGE_MAPPER, id);
        } catch (DataAccessException e) {
            log.error(e);
            return null;
        }
    }
}
